/**
 * StorageHelper.java
 * Copyright (C) 2009 Char Software Inc., DBA Localytics
 * 
 *  This code is provided under the Localytics Modified BSD License.
 *  A copy of this license has been distributed in a file called LICENSE
 *  with this source code.  
 *  
 *  Please visit www.localytics.com for more information.
 */

package com.Localytics.LocalyticsSession;

import net.rim.device.api.system.DeviceInfo;
import net.rim.device.api.system.PersistentStore;
import net.rim.device.api.system.PersistentObject;
import net.rim.device.api.util.StringUtilities;

import java.util.Vector;

/**
 * Static class which provides functions to help LocalyticsSession avoid having to know
 * the specifics of how the session is stored in the persisted store.
 */
public final class StorageHelper 
{        
    // Key global to all apps used to get the store location which stores the DeviceId. 
    private static final long DEVICE_ID_KEY = 0x75eab1eed15cb42eL; // com.Localytics.StorageHelper.deviceId        
    
    // The text to prepend any logged messages
    private static final String LOG_PREFIX = "(Localytics Storage)";
    
    private StorageHelper() {} // This class should not be instantiated.

    /**
     * Retrieves the DeviceId for this device from the persistent store.  
     * In older versions of the library, the ID was generated by the library and 
     * written to the persistent store.  As a result, this function now looks to see
     * if an old value is still around and uses that.  Otherwise the device PIN is
     * hashed and returned.  Simulators will always generate a unique ID and store it
     * so that each simulator will appear as a different device to somebody testing 
     * their integration.
     *      
     * @return A deviceId which will be consistent across all apps and sessions
     */    
    public static String getDeviceUUID()
    {           
        String deviceId = null;        
         
        // Check for the old deviceId.  This will only be present if this device
        // has run an application with the older version of the Localytics library or
        // if this is a simulator.
        PersistentObject deviceIdStore = PersistentStore.getPersistentObject(StorageHelper.DEVICE_ID_KEY);
        if(deviceIdStore != null)
        {
            deviceId = (String)deviceIdStore.getContents();            
            if(deviceId != null)  // if an old id was found, return it
            {
                return deviceId;
            }
            
            // if this was a simulator, and no ID was found, generate one.
            if(DeviceInfo.isSimulator())
            {
                deviceId = DatapointHelper.generateRandomId();
                deviceIdStore.setContents(deviceId);
                deviceIdStore.commit();
                return deviceId;
            }
        }
        
        // No previous ID was found, so the devicePIN can be used.
        return DeviceUUIDFromPin();        
    }    
    
    /**
     * Hashes's the device's PIN and returns it as a string
     * @return An id unique to this device
     */
    public static String DeviceUUIDFromPin()
    {
        String devicePIN = Integer.toString(DeviceInfo.getDeviceId(), 16);
        return Long.toString( StringUtilities.stringHashToLong(devicePIN), 16 );   
    }
    
    /**
     * Looks up the opt in state of a given application.  If no opt in state has 
     * been saved assumes the app is opted in and creates the state.
     * @param appKey The unique ID for the app to query
     * @return true if the app is opted in, false otherwise.
     */
    public static boolean isAppOptedIn(final String appKey)
    {
        // The app's opt in is stored in a persistent store object discovered by hashing the app's key.
        PersistentObject appStore = PersistentStore.getPersistentObject(StringUtilities.stringHashToLong(appKey));
        
        if(appStore != null)
        {
            Boolean optIn = (Boolean) appStore.getContents();
            if(optIn == null)
            {
                optIn = new Boolean(true);
                appStore.setContents(optIn);
                appStore.commit();
            }
            
            return optIn.booleanValue();
        }
        
        return false; // if the p store is not available, return false because something is broken.
    }        
          
    /**
     * Stores the opt in state for an application.  
     * @param appKey The unique identifier for the application
     * @param OptState The state to commit.  true = opted in, false = opted out
     */
    public static void storeAppOptin(final String appKey, final boolean optState)
    {
        // The app's opt in is stored in a persistent store object discovered by hashing the app's key.
        PersistentObject appStore = PersistentStore.getPersistentObject(StringUtilities.stringHashToLong(appKey));
        
        if(appStore != null)
        {
            Boolean optIn = new Boolean(optState);
            appStore.setContents(optIn);
            appStore.commit();
        }
    }
                            
    /**
     * Finds a new session slot in the Persisted store for this session and returns it.
     * If there are none available, it returns null so the app knows the session cannot
     * be opened.  Each slot is a different PersisentObject.  This is necessary because every time a 
     * session action is written the session has to be pulled from the store to append to it so if all 
     * the sessions are stored in one object then each write will require reading every session that is 
     * stored into memory.
     * @param appKey The key unique to this app.  This is necessary for finding the storage slots in the store.
     * @param maxNumSessions The total number of sessions to look for
     * @return A PersistentObject used later to store the session. Or null if there are no slots.
     */
    public static PersistentObject getNewSessionStore(final String appKey, final int maxNumSessions)
    {                
        int currentSlot;
            
        // Get all the store objects associated with this app key and look for a null slot.
        PersistentObject[] sessions = getSessionStores(appKey, maxNumSessions);
        for(currentSlot = 0; currentSlot < maxNumSessions; currentSlot++)
        {           
            if(sessions[currentSlot].getContents() == null)
            {
                return sessions[currentSlot];
            }
        }
        
        return null;
    }               
    
    /**
     * Commits a YAML blob to the session.  
     * @param sessionStore The store where this session is currently living.
     * @param dataBlob The blob to store.
     */
    public static void storeSessionBlob(final PersistentObject sessionStore, final String dataBlob)
    {        
        // Needs to be synchronized so that if two threads write an event at the same time
        // they both make it into the persisted store.  
        synchronized(sessionStore)
        {        
            Vector sessionVector = (Vector) sessionStore.getContents();            
            if(sessionVector == null)
            {
                sessionVector = new Vector();            
            }
            
            sessionVector.addElement(dataBlob);
            sessionStore.setContents(sessionVector);
            sessionStore.commit();
        }
    }    
    
    /**
     * Returns all of the persisted objects which can hold sessions. 
     * @param appKey The unique key idenitfying the application to return
     * @param maxNumSessions The number of sessions which can be allocated for that app's sessions
     * @return An array of Persisentobjects, one for each potential session slot
     */
    public static PersistentObject[] getSessionStores(final String appKey, final int maxNumSessions)
    {        
        final long appStorageKey = StringUtilities.stringHashToLong(appKey);
        int currentSlot;
        long session_key;
        
        PersistentObject[] sessionSlots = new PersistentObject[maxNumSessions];
        for(currentSlot = 0; currentSlot < maxNumSessions; currentSlot++)
        {
            session_key = appStorageKey + 1 + currentSlot;
            sessionSlots[currentSlot] = PersistentStore.getPersistentObject(session_key);
        }
        
        return sessionSlots;
    }
    
    /**
     * Prints the contents of the persisted store for any application.  This function is provided
     * to aid in testing and debugging of Localytics Integration.
     * @param appKey The app key of the application to dump.
     * @param maxNumSessions The number of sessions which can be stored.
     */
    public static void dumpStoreContents(final String appKey, final int maxNumSessions)
    {
        PersistentObject appStore = PersistentStore.getPersistentObject(StringUtilities.stringHashToLong(appKey));              
        
        debugMsg("============== BEGIN DUMP OF PERSISTED STORE ==============");                      
              
        // dump the opt in/out value
        Boolean optValue = (Boolean)appStore.getContents();
        if(optValue == null) {
            debugMsg("optin value is null");
        }
        else  {
            debugMsg("optin value is: " + optValue.toString());
        }
        
        // Go through each session slot and print it.
        int currentSlot;
        PersistentObject[] sessions = getSessionStores(appKey, maxNumSessions);
        for(currentSlot = 0; currentSlot < maxNumSessions; currentSlot++)        
        {        
            debugMsg("Examing slot: " + Integer.toString(currentSlot));
            if(sessions[currentSlot].getContents() == null)
            {
                debugMsg("That slot is empty.");
            }
            else
            {
                Vector v = (Vector)sessions[currentSlot].getContents();
                if(v == null)   {
                    debugMsg("The vector inside that key is empty.");
                }
                else 
                {                    
                    for(int i=0; i < v.size(); i++)  {
                        debugMsg(Integer.toString(i) + ": " + v.elementAt(i));
                    }
                }
            }
        }
                
        debugMsg("============== END DUMP OF PERSISTED STORE ==============");
    }
    
    /**
     * Prepends a String to a message and outputs it to the console
     * @param msg The message to output
     */
    private static void debugMsg(String msg)
    {
        System.out.println(StorageHelper.LOG_PREFIX + msg);
    }
} 
